package src;

import java.io.*;
import java.net.Socket;
import src.Players.*;
import src.NPCs.*;
import src.Util.*;
import src.World.*;
import src.Packets.*;

public class GameEngine
{
    public int maxPlayers = 900;
    public int maxNPCs = 150;
    public int maxListedNPCs = 7000;
    private long lastEntityUpdate = 0, lastItemUpdate = 0;
    public Map map;
    public Items item;
    public Shops shops;
    public Stream globalStream;
    public Packet pkt;
    public Frames frame;
    public Player[] players;
    public NPC[] npcs;
    public NPCList[] npcLists;
    public PlayerMovement moveP;
    public PlayerUpdate updateP;
    public NPCMovement moveN;
    public NPCUpdate updateN;
    // public SQL sql;

    public GameEngine()
    {
        players = new Player[maxPlayers];
        npcs = new NPC[maxNPCs];
        npcLists = new NPCList[maxListedNPCs];
        globalStream = new Stream(new byte[30000]);
        // map = new Map();
        // map.loadWorldMap();
        shops = new Shops();
        pkt = new Packet();
        frame = new Frames();
        moveP = new PlayerMovement();
        updateP = new PlayerUpdate();
        moveN = new NPCMovement();
        updateN = new NPCUpdate();
	// sql = new SQL();
    }

    public void load()
    {
        LoadNPCLists lnl = new LoadNPCLists();
        lnl = null;
        item = new Items();
    }

    public void newPlayer(Socket s, String hostAddress)
    {
        int index = 0;
        for(int i = 1; i < maxPlayers; i++)
        {
            if(players[i] == null)
            {
                index = i;
                break;
            }
        }
        if(index == -1)
        {
            Main.m.log("Max players are online, cannot login.");
            return;
        }
        if(s != null && Main.m != null)
        {
            players[index] = new Player(s, index);
        }
    }

    public void process()
    {
        if(System.currentTimeMillis() - lastEntityUpdate >= 600)
        {
            for(Player p : players)
            {
                if(p == null || pkt == null || moveP == null)
                {
                    continue;
                }
                else if(p.disconnected[0] && p.disconnected[1])
                {
                    removePlayer(p);
                    continue;
                }
                else if(p.isOnline)
                {
                    p.process();
                    moveP.postProcessing(p);
                    moveP.getNextPlayerMovement(p);
                }
            }
            for(Player p : players)
            {
                if(p != null && updateP != null && p.isOnline && !p.disconnected[0] && !p.disconnected[1])
                {
                    if(!p.initialized)
                    {
                        p.initialize();
                        p.initialized = true;
                    }
                    else
                    {
                        updateP.update(p);
                    }
                }
            }
            for(Player p : players)
            {
                if(p != null && updateP != null && p.isOnline)
                {
                    updateP.clearUpdateFlags(p);
                }
            }
            for(NPC n : npcs)
            {
                if(n != null && updateN != null)
                {
                    updateN.clearUpdateFlags(n);
                    n.process();
                    if(!n.isDead)
                    {
                        if(n.randomWalk && !n.isUnderPlayerAttack)
                        {
                            moveN.randomWalk(n);
                        }
                    }
                    else
                    {
                        if(!n.deadEmoteDone)
                        {
                            n.requestAnim(NPCAttack.getDeathAnimation(n.npcType), 0);
                            n.deadEmoteDone = true;
                            n.combatDelay = 3;
                        }
                        else if(n.deadEmoteDone && !n.hiddenNPC && n.combatDelay <= 0)
                        {
                            n.hiddenNPC = true;
                        }
                        else if(n.hiddenNPC && n.respawnDelay <= 0)
                        {
                            if(n.needsRespawn)
                            {
                                newNPC(n.npcType, n.makeX, n.makeY, n.heightLevel, n.moveRangeX1, n.moveRangeY1, 
                                n.moveRangeX2, n.moveRangeY2, true);
                            }
                            npcs[n.npcIndex] = null;
                            rebuildNPCs();
                        }
                    }
                }
            }
            lastEntityUpdate = System.currentTimeMillis();
        }
        if(System.currentTimeMillis() - lastItemUpdate >= 1000)
        {
            item.process();
            lastItemUpdate = System.currentTimeMillis();
        }
        for(Player p : players)
        {
            if(p != null && pkt != null && p.isOnline && !p.disconnected[0] && !p.disconnected[1])
            {
                if(p.outStream != null && p.outStream.getOffset() > 0)
                {
                    queueBytes(p, p.outStream);
                }
                pkt.packetProcess(p);
            }
        }
    }

    public void rebuildNPCs()
    {
        for(Player p : players)
        {
            if(p != null)
            {
                p.rebuildNPCList = true;
            }
        }
    }

    private void queueBytes(Player p, Stream str)
    {
        if(p == null || str == null || p.disconnected[0])
        {
            return;
        }
        for(int i = 0; i < str.getOffset(); i++)
        {
            p.getBuffer()[p.writePtr] = str.getBuffer()[i];
            p.writePtr = (p.writePtr + 1) % p.getBufferSize();
            if(p.writePtr == (p.readPtr + p.getBufferSize() - 1) % p.getBufferSize())
            {
                Main.m.log("[" + p.playerIndex + "-" + p.username + "] BUFFER OVERFLOW");
            }
        }
        str.resetOffset();
        int offset = -1, numBytesInBuffer = -1;
        offset = p.readPtr;
        if(p.writePtr >= p.readPtr)
        {
            numBytesInBuffer = p.writePtr - p.readPtr;
        }
        else
        {
            numBytesInBuffer = p.getBufferSize() - p.readPtr;
        }
        if(p.getBuffer() != null && offset != -1 && numBytesInBuffer > 0 && p.out != null)
        {
            try
            {
                p.out.write(p.getBuffer(), offset, numBytesInBuffer);
                p.readPtr = (p.readPtr + numBytesInBuffer) % p.getBufferSize();
                if(p.writePtr == p.readPtr)
                {
                    p.out.flush();
                }
            }
            catch(IOException ioe)
            {
                p.disconnected[0] = p.disconnected[1] = true;
            }
        }
    }

    private void removePlayer(Player p)
    {
        if(p != null)
        {
            if(p.isOnline && p.username != null && p.username != "" && p.password != "")
            {
                Main.m.log(p.username + " has logged out.");
                saveGame(p);
            }
            int index = p.playerIndex;
            p.destruct();
            players[index] = null;
        }
    }

    public boolean saveGame(Player plr)
    {
        PlayerSave tempSave = new PlayerSave(plr);
        try
        {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./src/Data/SavedGames/" + tempSave.username + ".dat"));
            out.writeObject((PlayerSave)tempSave);
            out.close();
        }
        catch(Exception e)
        {
            return false;
        }
        tempSave = null;
        return true;
    }

    public void loadGame(Player p)
    {
        PlayerSave tempPlayer;
        try
        {
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("./src/Data/SavedGames/" + p.username + ".dat"));
            tempPlayer = (PlayerSave)in.readObject();
            in.close();
        }
        catch(Exception e)
        {
            return;
        }
        p.heightLevel = tempPlayer.heightLevel;
        if(tempPlayer.absX > 0 && tempPlayer.absY > 0)
        {
            p.teleportToX = tempPlayer.absX;
            p.teleportToY = tempPlayer.absY;
        }
        p.password = tempPlayer.password;
        p.rights = tempPlayer.rights;
        p.items = tempPlayer.items;
        p.itemsN = tempPlayer.itemsN;
        p.equipment = tempPlayer.equipment;
        p.equipmentN = tempPlayer.equipmentN;
        p.bankItems = tempPlayer.bankItems;
        p.bankItemsN = tempPlayer.bankItemsN;
        p.skillLvl = tempPlayer.skillLvl;
        p.skillXP = tempPlayer.skillXP;
        tempPlayer = null;
    }

    public int getDistance(int coordX1, int coordY1, int coordX2, int coordY2)
    {
        int deltaX = coordX2 - coordX1;
        int deltaY = coordY2 - coordY1;
        return ((int)Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)));
    }

    public int getIdFromName(String playerName)
    {
        playerName.replaceAll("_", " ");
        for(Player p : players)
        {
            if(p != null)
            {
                if(p.username.equalsIgnoreCase(playerName))
                {
                    return p.playerIndex;
                }
            }
        }
        return 0;
    }

    public int newNPC(int type, int absX, int absY, int height, int mRX1, int mRY1, int mRX2, int mRY2, boolean needsRespawn)
    {
        int index = -1;
        for(int i = 1; i < maxNPCs; i++)
        {
            if(npcs[i] == null)
            {
                index = i;
                break;
           }
        }
        if(index == -1)
        {
            Main.m.log("Max number of NPCs spawned");
            return -1;
        }
        NPC n = npcs[index] = new NPC(type, index);
        n.absX = absX;
        n.absY = absY;
        n.makeX = absX;
        n.makeY = absY;
        n.heightLevel = height;
        n.moveRangeX1 = mRX1;
        n.moveRangeY1 = mRY1;
        n.moveRangeX2 = mRX2;
        n.moveRangeY2 = mRY2;
        n.needsRespawn = needsRespawn;
        NPCList nl = npcLists[type];
        if(nl != null)
        {
            n.name = nl.getName();
            n.combatLevel = nl.getCb();
            n.maxHP = nl.getHP();
            n.currentHP = n.maxHP;
            n.maxHit = nl.getMaxHit();
            n.atkType = nl.getAtk();
            n.weakness = nl.getWeakness();
            n.respawnDelay = nl.getSpawnTime();
        }
        return index;
    }

    public String getNPCName(int npcType)
    {
        if(npcType > maxListedNPCs)
        {
            return new String("NPCType = " + npcType);
        }
        else if(npcLists[npcType] != null)
        {
            return npcLists[npcType].getName();
        }
        else
        {
            return new String("NPCType = " + npcType);
        }
    }

    public boolean wildernessArea(int absX, int absY)
    {
        return (absX >= 2940 && absX <= 3395 && absY >= 3520 && absY <= 4000);
    }
}